其他
在 View 上使用挂起函数
Kotlin 协程 https://developer.android.google.cn/kotlin/coroutines
Android 视图 💘 回调
AnimatorListener 获取动画结束相关的事件 RecyclerView.OnScrollListener 获取滑动状态变更事件 View.OnLayoutChangeListener 获取 View 布局改变的事件
AnimatorListener https://developer.android.google.cn/reference/android/animation/Animator.AnimatorListener.html RecyclerView.OnScrollListener https://developer.android.google.cn/reference/androidx/recyclerview/widget/RecyclerView.OnScrollListener View.OnLayoutChangeListener https://developer.android.google.cn/reference/android/view/View.OnLayoutChangeListener.html
Runnable https://developer.android.google.cn/reference/java/lang/Runnable.html
KTX 扩展方法
View.doOnPreDraw() https://developer.android.google.cn/reference/kotlin/androidx/core/view/package-summary#doonpredraw View.doOnLayout() https://developer.android.google.cn/reference/kotlin/androidx/core/view/package-summary#doonlayout Animator.doOnEnd() https://developer.android.google.cn/reference/kotlin/androidx/core/animation/package-summary#(android.animation.Animator).doOnEnd(kotlin.Function1)
使用协程解决问题
jank https://developer.android.google.cn/topic/performance/vitals/render
suspendCancellableCoroutine
suspendCoroutine() https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/suspend-coroutine.html suspendCancellableCoroutine() https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/suspend-cancellable-coroutine.html
等待 View 被布局完成
suspend fun View.awaitNextLayout() = suspendCancellableCoroutine<Unit> { cont ->
// 这里的 lambda 表达式会被立即调用,允许我们创建一个监听器
val listener = object : View.OnLayoutChangeListener {
override fun onLayoutChange(...) {
// 视图的下一次布局任务被调用
// 先移除监听,防止协程泄漏
view.removeOnLayoutChangeListener(this)
// 最终,唤醒协程,恢复执行
cont.resume(Unit)
}
}
// 如果协程被取消,移除该监听
cont.invokeOnCancellation { removeOnLayoutChangeListener(listener) }
// 最终,将监听添加到 view 上
addOnLayoutChangeListener(listener)
// 这样协程就被挂起了,除非监听器中的 cont.resume() 方法被调用
}
viewLifecycleOwner.lifecycleScope.launch {
// 将该视图设置为不可见,再设置一些文字
titleView.isInvisible = true
titleView.text = "Hi everyone!"
// 等待下一次布局事件的任务,然后才可以获取该视图的高度
titleView.awaitNextLayout()
// 布局任务被执行
// 现在,我们可以将视图设置为可见,并其向上平移,然后执行向下的动画
titleView.isVisible = true
titleView.translationY = -titleView.height.toFloat()
titleView.animate().translationY(0f)
}
doOnPreDraw() https://developer.android.google.cn/reference/kotlin/androidx/core/view/package-summary#doonpredraw
作用域
Lifecycle https://developer.android.google.cn/reference/androidx/lifecycle/Lifecycle.html lifecycleScope https://developer.android.google.cn/topic/libraries/architecture/coroutines#lifecyclescope CoroutineScope https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/
LifecycleScope 被包含在 AndroidX 的 lifecycle-runtime-ktx 依赖库中,可以在这里找到更多信息
更多信息 https://developer.android.google.cn/topic/libraries/architecture/coroutines
viewLifecycleOwner https://developer.android.google.cn/reference/androidx/fragment/app/Fragment.html#getViewLifecycleOwner() lifecycleScope https://developer.android.google.cn/topic/libraries/architecture/coroutines#lifecyclescope
等待 Animator 执行完成
Animator https://developer.android.google.cn/reference/android/animation/Animator.html
suspend fun Animator.awaitEnd() = suspendCancellableCoroutine<Unit> { cont ->
// 增加一个处理协程取消的监听器,如果协程被取消,
// 同时执行动画监听器的 onAnimationCancel() 方法,取消动画
cont.invokeOnCancellation { cancel() }
addListener(object : AnimatorListenerAdapter() {
private var endedSuccessfully = true
override fun onAnimationCancel(animation: Animator) {
// 动画已经被取消,修改是否成功结束的标志
endedSuccessfully = false
}
override fun onAnimationEnd(animation: Animator) {
// 为了在协程恢复后的不发生泄漏,需要确保移除监听
animation.removeListener(this)
if (cont.isActive) {
// 如果协程仍处于活跃状态
if (endedSuccessfully) {
// 并且动画正常结束,恢复协程
cont.resume(Unit)
} else {
// 否则动画被取消,同时取消协程
cont.cancel()
}
}
}
})
}
invokeOnCancellation
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellable-continuation/invoke-on-cancellation.html
onAnimationCancel()
https://developer.android.google.cn/reference/android/animation/Animator.AnimatorListener.html#onAnimationCancel(android.animation.Animator)
组合使用
viewLifecycleOwner.lifecycleScope.launch {
ObjectAnimator.ofFloat(imageView, View.ALPHA, 0f, 1f).run {
start()
awaitEnd()
}
ObjectAnimator.ofFloat(imageView, View.TRANSLATION_Y, 0f, 100f).run {
start()
awaitEnd()
}
ObjectAnimator.ofFloat(imageView, View.TRANSLATION_X, -100f, 0f).run {
start()
awaitEnd()
}
}
AnimatorSet https://developer.android.google.cn/reference/android/animation/AnimatorSet.html
ValueAnimator https://developer.android.google.cn/reference/android/animation/ValueAnimator.html RecyclerView https://developer.android.google.cn/reference/androidx/recyclerview/widget/RecyclerView.html Animator https://developer.android.google.cn/reference/android/animation/Animator.html
viewLifecycleOwner.lifecycleScope.launch {
// #1: ValueAnimator
imageView.animate().run {
alpha(0f)
start()
awaitEnd()
}
// #2: RecyclerView smooth scroll
recyclerView.run {
smoothScrollToPosition(10)
// 该方法和其他方法类似,等待当前的滑动完成,我们不需要刻意关注实现
// 代码可以在文末的引用中找到
awaitScrollEnd()
}
// #3: ObjectAnimator
ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, -100f, 0f).run {
start()
awaitEnd()
}
}
ValueAnimator https://developer.android.google.cn/reference/android/animation/ValueAnimator ObjectAnimator https://developer.android.google.cn/reference/android/animation/ObjectAnimator async() https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
viewLifecycleOwner.lifecycleScope.launch {
val anim1 = async {
imageView.animate().run {
alpha(0f)
start()
awaitEnd()
}
}
val scroll = async {
recyclerView.run {
smoothScrollToPosition(10)
awaitScrollEnd()
}
}
// 等待以上两个操作全部完成
anim1.await()
scroll.await()
// 此时,anim1 和滑动都完成了,我们开始执行 ObjectAnimator
ObjectAnimator.ofFloat(textView, View.TRANSLATION_X, -100f, 0f).run {
start()
awaitEnd()
}
}
Animator.startDelay https://developer.android.google.cn/reference/android/animation/Animator.html#setStartDelay(long) delay() https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
viewLifecycleOwner.lifecycleScope.launch {
val anim1 = async {
// ...
}
val scroll = async {
// 我们希望在 anim1 完成后,延迟 200ms 执行滚动
delay(200)
recyclerView.run {
smoothScrollToPosition(10)
awaitScrollEnd()
}
}
// …
}
repeat()
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/repeat.html
viewLifecycleOwner.lifecycleScope.launch {
repeat(3) {
ObjectAnimator.ofFloat(textView, View.ALPHA, 0f, 1f, 0f).run {
start()
awaitEnd()
}
}
}
您甚至可以通过重复计数来实现更精妙的功能。假设您希望淡入淡出在每次重复中逐渐变慢:
viewLifecycleOwner.lifecycleScope.launch {
repeat(3) { repetition ->
ObjectAnimator.ofFloat(textView, View.ALPHA, 0f, 1f, 0f).run {
// 第一次执行持续 150ms,第二次:300ms,第三次:450ms
duration = (repetition + 1) * 150L
start()
awaitEnd()
}
}
}
最后
希望通过本文,您可以进一步思考协程还可以在哪些其他的 API 中发挥作用。
推荐阅读